package com.york.jobmew.auth.domain.service.impl;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import com.google.gson.Gson;
import com.york.jobmew.auth.common.enums.AuthUserStatusEnum;
import com.york.jobmew.auth.common.enums.IsDeletedFlagEnum;
import com.york.jobmew.auth.domain.constants.AuthConstant;
import com.york.jobmew.auth.domain.convert.AuthUserBOConverter;
import com.york.jobmew.auth.domain.entity.AuthUserBO;
import com.york.jobmew.auth.domain.exception.UserException;
import com.york.jobmew.auth.domain.redis.RedisUtil;
import com.york.jobmew.auth.domain.service.AuthUserDomainService;
import com.york.jobmew.auth.infra.basic.entity.*;
import com.york.jobmew.auth.infra.basic.service.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBitSet;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 用户领域service
 */
@Service
@Slf4j
public class AuthUserDomainServiceImpl implements AuthUserDomainService {

    @Resource
    private AuthUserService authUserService;

    @Resource
    private AuthUserRoleService authUserRoleService;

    @Resource
    private AuthPermissionService authPermissionService;

    @Resource
    private AuthRolePermissionService authRolePermissionService;

    @Resource
    private AuthRoleService authRoleService;
    /**
     * 配合盐值加密
     */
    private static final String salt = "jiechan";

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RedissonClient redissonClient;

    private static final String AUTH_PERMISSION_PREFIX = "auth.permission";

    private static final String AUTH_ROLE_PREFIX = "auth.role";

    private static final String LOGIN_PREFIX = "loginCode";

    /**
     * 用户注册服务方法
     * 实现用户注册、角色权限分配及缓存初始化全流程
     *
     * @Transactional 注解保证事务一致性（包含用户、角色、权限等所有数据库操作）
     */
    @Override
    @SneakyThrows  // Lombok注解，自动抛出受检异常
    @Transactional(rollbackFor = Exception.class)  // 事务配置，任何异常都触发回滚
    public Boolean register(AuthUserBO authUserBO) {

        // ==================== 1.用户基础信息校验 ====================
        // 构建查询条件（根据用户名查重）
        AuthUser existAuthUser = new AuthUser();
        existAuthUser.setUserName(authUserBO.getUserName());
        List<AuthUser> existUser = authUserService.queryByCondition(existAuthUser);

        // 用户已存在逻辑处理
        if (!existUser.isEmpty()) {
            return true;
        }

        // ==================== 2.用户实体初始化 ====================
        // DTO转换（BO -> Entity）
        AuthUser authUser = AuthUserBOConverter.INSTANCE.convertBOToEntity(authUserBO);

        // 密码加密处理（使用Sa-Token工具类md5加盐哈希）
        if (StringUtils.isNotBlank(authUser.getPassword())) {
            authUser.setPassword(SaSecureUtil.md5BySalt(authUser.getPassword(), salt));
        }

        // 设置默认信息（头像&昵称）
        if (StringUtils.isBlank(authUser.getAvatar())) {
            authUser.setAvatar("http://localhost:9000/jiechan/avator.jpg");
        }
        if (StringUtils.isBlank(authUser.getNickName())) {
            authUser.setNickName("街蝉");
        }

        // 设置状态标志
        authUser.setStatus(AuthUserStatusEnum.OPEN.getCode());
        authUser.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());

        // ==================== 3.用户数据持久化 ====================
        int count = authUserService.insert(authUser);  // 返回影响行数

        // ==================== 4.角色权限初始化 ====================
        // 获取基础角色（普通用户角色）
        AuthRole authRole = new AuthRole();
        authRole.setRoleKey(AuthConstant.NORMAL_USER);
        AuthRole roleResult = authRoleService.queryByCondition(authRole);
        Long roleId = roleResult.getId();

        // 构建用户-角色关联关系
        AuthUserRole authUserRole = new AuthUserRole();
        authUserRole.setUserId(authUser.getId());
        authUserRole.setRoleId(roleId);
        authUserRole.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
        authUserRoleService.insert(authUserRole);

        // ==================== 5.角色权限缓存处理 ====================
        // 缓存角色信息到Redis（使用Gson序列化）
        String roleKey = redisUtil.buildKey(AUTH_ROLE_PREFIX, authUser.getUserName());
        List<AuthRole> roleList = Collections.singletonList(authRole);
        redisUtil.set(roleKey, new Gson().toJson(roleList));

        // 查询角色对应的权限列表
        AuthRolePermission authRolePermission = new AuthRolePermission();
        authRolePermission.setRoleId(roleId);
        List<AuthRolePermission> rolePermissionList = authRolePermissionService.queryByCondition(authRolePermission);
        List<Long> permissionIdList = rolePermissionList.stream().map(AuthRolePermission::getPermissionId).collect(Collectors.toList());

        // 缓存权限信息到Redis
        List<AuthPermission> permissionList = authPermissionService.queryByRoleList(permissionIdList);
        String permissionKey = redisUtil.buildKey(AUTH_PERMISSION_PREFIX, authUser.getUserName());
        redisUtil.set(permissionKey, new Gson().toJson(permissionList));

        // ==================== 6.结果返回 ====================
        return count > 0;  // 返回注册结果（仅反映用户表插入情况）
    }

    @Override
    public Boolean update(AuthUserBO authUserBO) {
        AuthUser authUser = AuthUserBOConverter.INSTANCE.convertBOToEntity(authUserBO);
        Integer count = authUserService.updateByUserName(authUser);
        return count > 0;
    }

    @Override
    public Boolean delete(AuthUserBO authUserBO) {
        AuthUser authUser = new AuthUser();
        authUser.setId(authUserBO.getId());
        authUser.setIsDeleted(IsDeletedFlagEnum.DELETED.getCode());
        Integer count = authUserService.update(authUser);
        //有任何的更新，都要与缓存进行同步的修改
        return count > 0;
    }

    /**
     * 验证码登陆
     *
     * @param validCode 验证码
     *                  device    设备信息
     * @return
     */
    @Override
//    public SaTokenInfo doLogin(String validCode, String device) {
    public SaTokenInfo doLogin(String validCode) {
        String loginKey = redisUtil.buildKey(LOGIN_PREFIX, validCode);
        String openId = redisUtil.get(loginKey);
        if (StringUtils.isBlank(openId)) {
            // 说明没有对应用户存在
            throw new UserException("暂未查到用户信息");
        }
        AuthUserBO authUserBO = new AuthUserBO();
        authUserBO.setUserName(openId);
        // 解决this.fun()事务失效的问题，打开AOP代理
        AuthUserDomainServiceImpl service = (AuthUserDomainServiceImpl) AopContext.currentProxy();
        service.register(authUserBO);

        // 使用 Sa-Token 进行登录,加入同端登录检测
//        StpUtil.login(openId, device);
        StpUtil.login(openId);
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        return tokenInfo;
    }

    /**
     * 账号密码登录
     *
     * @param authUserBO 用户登录信息（包含用户名和密码）
     *                   device     设备信息
     * @return SaTokenInfo
     */
    @Override
//    public SaTokenInfo doLoginByPassword(AuthUserBO authUserBO, String device) {
    public SaTokenInfo doLoginByPassword(AuthUserBO authUserBO) {
        // ==================== 1.查询用户信息 ====================
        // 构建查询条件
        AuthUser queryUser = new AuthUser();
        queryUser.setUserName(authUserBO.getUserName());
        List<AuthUser> userList = authUserService.queryByCondition(queryUser);
        // 用户不存在
        if (CollectionUtils.isEmpty(userList)) {
            throw new UserException("用户不存在");
        }
        // 获取用户实体
        AuthUser authUser = userList.get(0);

        // ==================== 2.密码校验 ====================
        // 对用户输入的密码进行加密（使用相同的盐值）
        String encryptedPassword = SaSecureUtil.md5BySalt(authUserBO.getPassword(), salt);

        // 比较加密后的密码
        if (!encryptedPassword.equals(authUser.getPassword())) {
            throw new UserException("密码错误");
        }

        // ==================== 3.登录状态处理 ====================
        // 使用 Sa-Token 进行登录,加入同端登录检测
//        StpUtil.login(authUser.getUserName(), device);
        StpUtil.login(authUser.getUserName());

        // 获取 Token 信息
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();

        // ==================== 4.返回结果 ====================
        return tokenInfo;
    }

    /**
     * 获取用户信息
     *
     * @param authUserBO
     * @return
     */
    @Override
    public AuthUserBO getUserInfo(AuthUserBO authUserBO) {
        AuthUser authUser = new AuthUser();
        authUser.setUserName(authUserBO.getUserName());
        List<AuthUser> userList = authUserService.queryByCondition(authUser);
        if (CollectionUtils.isEmpty(userList)) {
            return new AuthUserBO();
        }
        AuthUser user = userList.get(0);
        return AuthUserBOConverter.INSTANCE.convertEntityToBO(user);
    }

    /**
     * 根据idList获取用户信息
     *
     * @param userNameList
     * @return
     */
    @Override
    public List<AuthUserBO> listUserInfoByIds(List<String> userNameList) {
        List<AuthUser> userList = authUserService.listUserInfoByIds(userNameList);
        if (CollectionUtils.isEmpty(userList)) {
            return Collections.emptyList();
        }
        return AuthUserBOConverter.INSTANCE.convertEntityToBO(userList);
    }

    /**
     * 获取当前登录信息
     *
     * @param loginId
     * @return
     */
    @Override
    public AuthUserBO getUserByLoginId(String loginId) {
        AuthUser authUser = new AuthUser();
        authUser.setUserName(loginId);
        List<AuthUser> userList = authUserService.queryByCondition(authUser);
        if (CollectionUtils.isEmpty(userList)) {
            return new AuthUserBO();
        }
        AuthUser user = userList.get(0);

        // 做脱敏处理
        if (StringUtils.isBlank(user.getNickName())) {
            user.setNickName("街蝉粉丝");
        }
        if (StringUtils.isBlank(user.getPhone())) {
            user.setPhone("暂未绑定手机号");
        }
        if (StringUtils.isBlank(user.getEmail())) {
            user.setEmail("暂未绑定邮箱");
        }
        if (StringUtils.isBlank(user.getAvatar())) {
            // 无头像，使用默认头像
            user.setAvatar("http://localhost:9000/jiechan/avator.jpg");
        }
        if (StringUtils.isBlank(user.getIntroduce())) {
            user.setIntroduce("暂无简介");
        }
        if (StringUtils.isBlank(user.getExtJson())) {
            user.setExtJson("暂无介绍");
        }
        // 密码脱敏
        user.setPassword(null);
        return AuthUserBOConverter.INSTANCE.convertEntityToBO(user);
    }

    /**
     * 添加用户签到记录
     *
     * @param userId 用户 id
     * @return 当前用户是否已签到成功
     */
    @Override
    public boolean addUserSignIn(long userId) {
        LocalDate today = LocalDate.now();
        String key = redisUtil.buildUserSignInKey(today.getYear(), userId);
        RBitSet signInBitSet = redissonClient.getBitSet(key);
        // 获取当前日期是一年中的第几天，作为偏移量（从 1 开始计数）
        int offset = today.getDayOfYear();
        // 查询当天有没有签到
        if (!signInBitSet.get(offset)) {
            // 如果当前未签到，则设置
            signInBitSet.set(offset, true);
        }
        // 当天已签到
        return true;
    }

    /**
     * 获取用户某个年份的签到记录
     *
     * @param userId 用户 id
     * @param year   年份（为空表示当前年份）
     * @return 签到记录映射
     */
    @Override
    public List<Integer> getUserSignInRecord(long userId, Integer year) {
        if (year == null) {
            LocalDate date = LocalDate.now();
            year = date.getYear();
        }
        String key = redisUtil.buildUserSignInKey(year, userId);
        // 获取 Redis 的 BitMap
        RBitSet signInBitSet = redissonClient.getBitSet(key);
        // 加载 BitSet 到内存中，避免后续读取时发送多次请求
        BitSet bitSet = signInBitSet.asBitSet();
        // 统计签到的日期
        List<Integer> dayList = new ArrayList<>();
        // 从索引 0 开始查找下一个被设置为 1 的位
        int index = bitSet.nextSetBit(0);
        while (index >= 0) {
            dayList.add(index);
            // 继续查找下一个被设置为 1 的位
            index = bitSet.nextSetBit(index + 1);
        }
        return dayList;
    }
}
